﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;

namespace xReplace
{
    /*
     * TODO: Add input and output plugin modules for different filetypes.
     */
    public class xReplaceCore
    {
        public delegate void status(int current, int max, bool verbose);
        public delegate void output(string text);
        public delegate void error(string text);

        public static output outputHandler = null;
        public static error errorHandler = null;
        public static status statusHandler = null;

        public XmlNodeList plugins = null;

        public xReplaceCore(status stHnd, output outHnd, error errHnd)
        {
            outputHandler = outHnd;
            errorHandler = errHnd;
            statusHandler = stHnd;
        }

        public void Run(string projectFilename, InvocationOptions options)
        {

            //string overrideSourcePath, string overrideDestinationPath, string overrideDestinationSingleFile
            string overrideSourcePath = (options.IsOptionSet(InvocationOptions.InvocationSwitch.overrideSourcePath)) ? options.GetOption(InvocationOptions.InvocationSwitch.overrideSourcePath)[0].Value : "";
            string overrideDestinationPath = (options.IsOptionSet(InvocationOptions.InvocationSwitch.overrideDestinationPath)) ? options.GetOption(InvocationOptions.InvocationSwitch.overrideDestinationPath)[0].Value : "";
            string overrideDestinationSingleFile = (options.IsOptionSet(InvocationOptions.InvocationSwitch.overrideDestinationSingleFile)) ? options.GetOption(InvocationOptions.InvocationSwitch.overrideDestinationSingleFile)[0].Value : "";

            outputHandler.Invoke("xReplace Engine " + this.GetType().Assembly.GetName().Version.Major + "." + this.GetType().Assembly.GetName().Version.Minor + "." + this.GetType().Assembly.GetName().Version.MajorRevision);
            outputHandler.Invoke("(C)2008 Joe Cincotta, Pixolüt Industries. http://www.pixolut.com ");
            if (projectFilename=="")
            {
                outputHandler.Invoke("xReplace.exe [filename] where file is an XML replace-schema project file.");
                outputHandler.Invoke("See documentation for more info...");
                return;
            }

            DebugObject debug = null;

            try
            {
                Project p = new Project(projectFilename);

                bool verboseOutput = p.GetKeyAsBoolean("verbose");

                //debug handler
                debug = new DebugObject(p);
                if(debug.enabled) outputHandler.Invoke("Debug mode enabled.");

                //Dump the script comment
                outputHandler.Invoke(p.GetKey("comment"));

                //read all plugin references
                plugins = p.GetNodeList("plugins/using");

                //set up the input
                InputObject input = new InputObject(p, overrideSourcePath);

                //set up the output
                OutputObject output = new OutputObject(p, overrideDestinationPath, overrideDestinationSingleFile);
                
                //read in the transforms
                XmlNode transforms = p.GetNode("transformations");
                XmlNodeList tl = transforms.ChildNodes;

                //for logic operators
                Stack<XmlNodeList> cmdStack = new Stack<XmlNodeList>();
                Stack<int> ptrStack = new Stack<int>();

                //cleaning...
                if (output.clean)
                {
                    if (verboseOutput) outputHandler.Invoke("Cleaning...");
                    if (output.singleFile != "")
                    {
                        if (File.Exists(output.singleFile)) { File.Delete(output.singleFile); }
                    }
                    else
                    {
                        if (Directory.Exists(output.path)) Directory.Delete(output.path, true);
                    }
                }

                //read the file list
                if (verboseOutput) outputHandler.Invoke("Reading...");
                string[] files = input.GetFiles();


                //process all files
                for (int fIdx = 0; fIdx < files.Length; fIdx++)
                {
                    //Progress...
                    //if (verboseOutput) outputHandler.Invoke("Processing: " + files[fIdx]);
                    statusHandler.Invoke((fIdx + 1) ,files.Length, verboseOutput);

                    if (input.CanInclude(files[fIdx]))
                    {
                        /*
                         * Define output filename
                         */
                        string outputFilename = output.GetOutputFilename(input, files[fIdx]);

                        /*
                         * Define file macros for the content pipeline
                         */
                        Content pipes = (options.IsOptionSet(InvocationOptions.InvocationSwitch.label))? new Content(options.GetOption(InvocationOptions.InvocationSwitch.label)) : new Content();
                        pipes.Commit = true;
                        pipes.DefaultContent = (input.doNotReadContent) ? "" : File.ReadAllText(files[fIdx]);
                        pipes.AbsoluteFilename = files[fIdx];
                        pipes.RelativeFilename = files[fIdx].Replace(input.path, "");
                        pipes.AbsoluteOutputFilename = outputFilename;
                        pipes.RelativeOutputFilename = outputFilename.Replace(output.path, "");

                        /*
                         * Set up the command interpreter for this pipeline
                         */
                        Dictionary<string, AbstractProcessingCommand> cmdList = GetCommands(ref pipes);
                        AbstractProcessingCommand tmpCmd;


                        if (debug.enabled)
                        {
                            debug.Log("Input:");
                            debug.Log(pipes.AbsoluteFilename);
                            debug.Log("Output:");
                            debug.Log(pipes.AbsoluteOutputFilename);
                        }


                        //transform pipeline
                        int ptr = 0;
                        while(ptr < tl.Count)
                        {

                            XmlNodeList param = tl[ptr].ChildNodes;
                            string transformContext = tl[ptr].Name.ToLower();

                            if (transformContext == "#comment")
                            {
                                if(debug.enabled) debug.Log("## " + tl[ptr].InnerText);
                            }
                            else if (transformContext == "if")
                            {

                                string label = GetAttribute(tl[ptr],"label");
                                string startsWith = GetAttribute(tl[ptr], "startsWith");
                                string endsWith = GetAttribute(tl[ptr], "endsWith");
                                string match = GetAttribute(tl[ptr], "match");
                                string notmatch = GetAttribute(tl[ptr], "notMatch");
                                string equals = GetAttribute(tl[ptr],"equals");
                                string notequal = GetAttribute(tl[ptr], "notEquals");
                                string empty = GetAttribute(tl[ptr], "empty");
                                string notStartsWith = GetAttribute(tl[ptr], "notStartsWith");
                                string notEndsWith = GetAttribute(tl[ptr], "notEndsWith");

                                string tmpStr = (label=="")?pipes.DefaultContent:pipes.LoadContent(label);
                                bool satisfied = false;

                                if ((empty != "") && (empty.ToLower() == "true") && (tmpStr == "")) satisfied = true;
                                if ((startsWith != "") && (tmpStr.StartsWith(startsWith))) satisfied = true;
                                if ((endsWith != "") && (tmpStr.EndsWith(endsWith))) satisfied = true;
                                if ((match != "") && (tmpStr.IndexOf(match) > -1)) satisfied = true;
                                if ((notmatch != "") && (tmpStr.IndexOf(notmatch) < 0)) satisfied = true;
                                if ((equals != "") && (tmpStr == equals)) satisfied = true;
                                if ((notequal != "") && (tmpStr != equals)) satisfied = true;
                                if ((notStartsWith != "") && (!tmpStr.StartsWith(startsWith))) satisfied = true;
                                if ((notEndsWith != "") && (!tmpStr.EndsWith(endsWith))) satisfied = true;

                                if (satisfied)
                                {
                                    cmdStack.Push(tl);
                                    ptrStack.Push(ptr);
                                    tl = tl[ptr].ChildNodes;
                                    ptr = 0;
                                }
                            }
                            else if (transformContext == "debug")
                            {
                                string message = GetAttribute(tl[ptr], "message");
                                debug.Log(message, ptr, tl, pipes);
                            }
                            else
                            {
                                //Acquire Command
                                tmpCmd = null;
                                try
                                {
                                    tmpCmd = cmdList[transformContext];
                                }
                                catch (Exception ex)
                                {
                                    errorHandler.Invoke("Command <" + transformContext + "> is not supported.\nCheck your syntax and make sure plug-ins are correctly referenced.");
                                    if (debug.enabled) debug.Log("Command <" + transformContext + "> is not supported. \n" + ex.ToString());
                                }
                                //Execute Command
                                try
                                {
                                    if (!tmpCmd.ProcessCommand(param))
                                    {
                                        pipes.Commit = false;
                                        break;
                                    }
                                }
                                catch (Exception ex)
                                {
                                    debug.Log(ex.ToString(), ptr, tl, pipes);
                                    throw new Exception("Error at action " + ptr + ", command <" + transformContext + ">.\nDetails: " + tl[ptr].InnerXml + "\nError details: " + ex.Message, ex);
                                }
                            }
                            ptr++;
                            if ((ptr == tl.Count) && (cmdStack.Count > 0))
                            {
                                tl = cmdStack.Pop();
                                ptr = ptrStack.Pop();
                            }
                        }
                        /*
                         * Output the content based on rules.
                         */
                        if (pipes.Commit)
                        {
                            //output pipeline
                            if (verboseOutput) outputHandler.Invoke("Processing: " + files[fIdx]);
                            output.WriteContent(outputFilename, pipes.DefaultContent);
                            //if (verboseOutput) outputHandler.Invoke("Written");
                        }
                        //else
                        //{
                        //    if (verboseOutput) outputHandler.Invoke("Excluded by content");
                        //}
                    }
                    //else
                    //{
                    //    if (verboseOutput) outputHandler.Invoke("Excluded by filename or depth");
                    //}
                }
            }
            catch (Exception ex)
            {
                if (debug != null) debug.Log(ex.ToString());
                errorHandler.Invoke(ex.Message);
            }
            outputHandler.Invoke("Completed...");
        }


        /*
         * Provide commands for the interpreter
         */
        private Dictionary<string, AbstractProcessingCommand> GetCommands(ref Content pipes)
        {
            Dictionary<string, AbstractProcessingCommand> cmdList = new Dictionary<string, AbstractProcessingCommand>();
            cmdList.Add("delete", ((AbstractProcessingCommand)new DeleteCommand(ref pipes)));
            cmdList.Add("load", ((AbstractProcessingCommand)new LoadCommand(ref pipes)));
            cmdList.Add("store", ((AbstractProcessingCommand)new StoreCommand(ref pipes)));
            cmdList.Add("replace", ((AbstractProcessingCommand)new ReplaceCommand(ref pipes)));
            cmdList.Add("exclude", ((AbstractProcessingCommand)new ExcludeCommand(ref pipes)));
            cmdList.Add("insert", ((AbstractProcessingCommand)new InsertCommand(ref pipes)));
            cmdList.Add("append", ((AbstractProcessingCommand)new AppendCommand(ref pipes)));
            cmdList.Add("print", ((AbstractProcessingCommand)new PrintCommand(ref pipes)));
            cmdList.Add("cmdrun", ((AbstractProcessingCommand)new CmdRunCommand(ref pipes)));

            for (int i = 0; i < plugins.Count; i++)
            {
                try
                {
                    string commandName = plugins[i].Attributes["command"].Value;
                    string details = plugins[i].InnerText.Trim();
                    cmdList.Add(commandName, ((AbstractProcessingCommand)Activator.CreateInstance(Type.GetType(details), new object[] { pipes })));
                }
                catch (Exception ex)
                {
                    errorHandler.Invoke("An error occurred when attempting to register a plugin: " + ex.Message + "\n\nThe offending command was:\n" + plugins[i].InnerXml);
                }

            }
            return cmdList;
        }

        private string GetAttribute(XmlNode node, string attributeName)
        {
            if (node.Attributes[attributeName] == null) return "";
            return node.Attributes[attributeName].Value;
        }
    }
}
